# ======================================
# 只要修改界面布局等均要执行该段代码
# ======================================
# pyuic5 yolov5_LLL.ui -o yolov5_LLL.py
# ======================================

from random import random
from PyQt5.QtGui import QColor
from yolov5_LLL import Ui_MainWindow
from PyQt5.QtWidgets import QFileDialog, QHBoxLayout, QDialog
import sys
import torch
import torch.backends.cudnn as cudnn
import json
import datetime
import csv
from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import numpy as np
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QPushButton
from utils.torch_utils import smart_inference_mode
from utils.plots import Annotator, colors, save_one_box
from pathlib import Path
from PyQt5.QtGui import QPixmap, QImage
from PyQt5 import QtGui, QtCore, QtWidgets
from utils.torch_utils import select_device
from utils.general import (Profile, check_file, check_img_size, check_imshow, cv2,
                           increment_path, non_max_suppression, scale_boxes, xyxy2xywh)
from PyQt5.QtCore import pyqtSignal, QObject

from PyQt5.QtWidgets import QTableWidgetItem
from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem
from PIL import Image
import os
import glob
import time

save_path1 = './OUTPUT/'
global stopp

from PyQt5.QtGui import QColor, QPixmap, QIcon, QPalette, QBrush

'''
============================================================================================================================
'''
import shutil


def mkdir(path):
    # 判断目录是否存在
    # 存在：True
    # 不存在：False
    '''
        如果文件夹不存在就创建，如果文件存在就清空！
    '''
    if not os.path.exists(path):
        os.mkdir(path)
    else:
        shutil.rmtree(path)
        os.mkdir(path)
    # 如果目录已存在，则不创建，提示目录已存在


'''
============================================================================================================================
'''


# 开启多线程
class MyThread(QThread):
    # pyqtSignal是一个用于在类之间进行通信的信号，它可以连接到pyqt5方法信号并执行相应的操作。
    # 发送图片信号槽
    send_img = pyqtSignal(np.ndarray)
    # 统计全部的数量
    send_total = pyqtSignal(dict)
    # 标签类型统计出现次数
    send_detectinfo_dic = pyqtSignal(dict)
    # 发送百分比
    send_percent = pyqtSignal(int)
    send_xunhuan = pyqtSignal(int)
    # 检测速度
    detect_speed = pyqtSignal(str)

    def __init__(self):
        # 调用父类多线程，继承方法
        super(MyThread, self).__init__()
        self.weights = "yolov5s.pt"  # 默认权重
        self.conf = 0.25  # 置信度
        self.iou = 0.45
        self.play = True  # 播放状态，默认为假，如果再按一次暂停
        self.is_save_result = 0  # 如果是0不保存，如果是2保存。默认不保存
        self.end_loop = False  # 跳出循环
        self.names_dic = {}  # 初始化字典

    # 重写多线程run方法，当开启多线程时会调用此方法,@torch.no_grad()主要用于禁用梯度计算，而@smart_inference_mode()主要用于在推理模式下自动管理内存。
    @smart_inference_mode()
    def run(self,
            imgsz=(640, 640),  # 网络模型训练的尺寸
            max_det=1000,  # 每张图像的最大检测数量，这里设置为1000个
            data='data/coco128.yaml',  # 数据集配置文件的路径，这里使用的是COCO数据集的128类版本
            device='',  # 使用的计算设备，可以是CPU或GPU
            view_img=False,  # 是否显示检测结果，这里设置为不显示
            save_txt=True,  # 是否将检测结果保存为文本文件，这里设置为不保存。
            save_conf=True,  # 是否在保存的标签中包含置信度信息，这里设置为不保存。
            save_crop=True,  # 是否保存裁剪后的预测框，这里设置为不保存
            nosave=False,  # 是否不保存图片或视频，这里设置为不保存
            classes=None,  # 根据类别进行过滤，可以指定一个或多个类别，例如--class 0,1,2表示只保留类别为0、1和2的检测结果。
            agnostic_nms=False,  # 是否使用无类别特定的非极大值抑制（NMS）
            augment=False,  # 是否使用数据增强进行推理
            visualize=False,  # 是否可视化特征图
            update=False,  # 是否更新所有模型
            project=save_path1,  # 保存结果的目录
            name='exp',  # : 保存结果的文件名前缀
            exist_ok=False,  # 如果存在同名的项目或文件，是否覆盖
            line_thickness=None,  # 边界框的线条粗细（以像素为单位），这里设置为2
            hide_labels=False,  # 是否隐藏标签
            hide_conf=False,  # 是否隐藏置信度
            half=False,  # 是否使用半精度浮点数进行推理
            dnn=False,  # 是否使用OpenCV的DNN模块进行ONNX推理
            vid_stride=1,  # 视频帧率步长，这里设置为1，表示每帧都进行处理
            ):
        try:

            # 选择要使用的设备（如GPU或CPU）。
            device = select_device(device)
            # 如果设备不是CPU，则将变量half设置为True，表示使用半精度浮点数进行计算。
            half &= device.type != 'cpu'
            # 加载模型权重文件到指定设备上。
            model = DetectMultiBackend(self.weights, device=device, dnn=dnn, data=data, fp16=half)
            # 从模型中分别获取：步长、名称列表、权重
            stride, names, pt = model.stride, model.names, model.pt
            names_dic = {}  # 初始化类目容器
            # 提取valuename，赋值初始个数0，最终格式如{person：0}
            for key, value in names.items():
                names_dic[value] = 0
            num_params = 0
            current_frame = 0  # 初始化计数器，用于计算视频的进度
            progress_percentage = 0
            # 遍历模型的所有参数，计算数量总和
            for param in model.parameters():
                num_params += param.numel()
            # 根据步长调整图像大小。
            imgsz = check_img_size(imgsz, s=stride)

            # 如果支持GPU运行，将模型转换为半精度浮点数
            if half:
                model.half()

            # 上面加载设备和模型参数，现在开始加载要检测的资源
            source = str(self.source)

            save_img = not nosave and not source.endswith('.txt')  # 这个变量用于决定是否保存推理图像。
            is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)  # 用于判断source是否是一个文件
            webcam = source.isnumeric() or source.endswith('.streams')  # 这个变量用于判断是否是网络摄像头
            bs = 1  # 批处理大小
            # 如何图片路径和资源存在，调整合适尺寸
            if is_file:
                source = check_file(source)  # 下载文件并返回文件路径
            if webcam:  # 如果数据源是网络摄像头
                view_img = check_imshow(warn=True)  # 调用check_imshow()函数检查当前环境是否支持显示图像，并将结果赋值给view_img
                cudnn.benchmark = True  # 设置cudnn的基准模式为True。
                datasetStreams = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt,
                                             vid_stride=vid_stride)  # 加载视频资源，设置大小和步长
                bs = len(datasetStreams)  # 获取数据集的长度，即视频帧数。
            else:
                # 来自于图片和视频
                datasetStreams = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
            vid_path, vid_writer = [None] * bs, [None] * bs  # 初始化两个列表，分别用于存储视频路径和视频写入器

            # 创建用于保存结果的目录
            # save_dir = increment_path(Path(save_path1) / name, exist_ok=exist_ok)
            # save_dir = increment_path(Path(save_path1) / name, exist_ok=exist_ok)
            save_dir = Path(save_path1)
            os.makedirs(save_dir, exist_ok=True)

            if self.is_save_result == 2:
                # 只有用户勾选保存的时候，才会创建文件夹
                (save_dir if save_txt else save_dir).mkdir(parents=True, exist_ok=True)

            # 预热模型，即让模型在开始实际处理之前先进行一次或多次的预加载和初始化操作
            model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))
            seen, windows, dt = 0, [], (Profile(), Profile(),
                                        Profile())  # seen用于记录已经处理过的图像数量；windows是一个列表，可能用于存储一些窗口信息；dt是一个包含三个Profile()对象的元组，可能用于性能分析

            dataset = iter(datasetStreams)  # 创建迭代器，遍历资源

            while True:
                if self.end_loop:
                    # 如果为真，跳出循环
                    self.send_percent.emit(0)
                    # 如果是摄像头，设置状态，停止线程
                    if webcam:
                        datasetStreams.stop_threads = True
                    break
                if self.play:
                    # for path, im, im0s, vid_cap, s in dataset:
                    # 如果是检测单张图片，不会自动跳出循环，继续检测会报错，所以try捕获空异常跳出循环
                    try:
                        path, im, im0s, vid_cap, s = next(dataset)
                    except Exception as e:
                        # 如果是图片检测结束了，跳出循环
                        break

                    # 如果是视频，检测进度
                    if vid_cap:
                        # 更新当前帧索引
                        current_frame += 1
                        # 计算并发送当前检测的进度百分比
                        progress_percentage = int(
                            current_frame / int(vid_cap.get(cv2.CAP_PROP_FRAME_COUNT)) * 100)
                        self.send_percent.emit(progress_percentage)

                    with dt[0]:  # 用来测量将numpy数组转换为PyTorch张量的时间

                        im = torch.from_numpy(im).to(model.device)
                        im = im.half() if model.fp16 else im.float()
                        im /= 255  # 归一化
                        if len(im.shape) == 3:
                            im = im[None]
                    with dt[1]:  # 用来测量模型预测的时间
                        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
                        pred = model(im, augment=augment, visualize=visualize)

                    with dt[2]:  # 用来测量非极大值抑制（NMS）的时间
                        pred = non_max_suppression(pred, self.conf, self.iou, classes, agnostic_nms, max_det=max_det)

                    # 处理预测结果，是yolov5官方代码，可以不用理会
                    for i, det in enumerate(pred):

                        seen += 1
                        if webcam:
                            p, im0, frame = path[i], im0s[i].copy(), dataset.count
                            s += f'{i}: '

                        else:
                            p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)

                        p = Path(p)  # to Path
                        save_path = str(save_dir / p.name)  # im.jpg
                        txt_path = str(save_dir / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt

                        '''
                        ================================================================================================
                        '''
                        with open(f'{txt_path}.txt', 'a') as f:
                            pass
                        file_path_left = save_dir / 'left' / f'{p.stem}.txt'

                        # 获取文件夹路径
                        folder_path_left = os.path.dirname(file_path_left)

                        # 检查文件夹是否存在，如果不存在，则创建
                        if not os.path.exists(folder_path_left):
                            os.makedirs(folder_path_left)

                        # 如果文件不存在，则创建空文件
                        if not os.path.exists(file_path_left):
                            open(file_path_left, 'a').close()

                        with open(file_path_left, 'a', encoding='utf-8') as file:
                            pass
                        '''
                        ================================================================================================
                        '''

                        csv_path = str(save_dir / 'results')
                        img_name = str(p.stem)
                        s += '%gx%g ' % im.shape[2:]  # print string
                        gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
                        imc = im0.copy() if save_crop else im0  # for save_crop
                        annotator = Annotator(im0, line_width=line_thickness, example=str(names))

                        if len(det):
                            # 在处理每一帧开始时，重置names_dic
                            for key, value in names.items():
                                names_dic[value] = 0

                            # Rescale boxes from img_size to im0 size
                            det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()

                            # Print results
                            for c in det[:, 5].unique():
                                n = (det[:, 5] == c).sum()  # detections per class
                                s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string

                            # 检测到物体了，画框.如果没检测到物体不会执行循环
                            count = 1
                            # file_path = save_dir / 'left' / f'{p.stem}.txt'
                            fp = '2.txt'
                            # with open(file_path, 'w') as file:
                            #     file.truncate(0)  # 清除1.txt文档内容

                            with open(fp, 'w') as file:
                                file.truncate(0)  # 清除2.txt文档内容

                            with open(f'{csv_path}.csv', 'a', newline='') as csv_file:
                                # 定义CSV写入器
                                writer = csv.DictWriter(csv_file, fieldnames=["序号ID", "文件路径", "目标类别",
                                                                              "坐标位置",
                                                                              "评判可信度", "目标面积",
                                                                              "目标占比", "检测时间"])

                                # 写入表头
                                writer.writeheader()

                            for *xyxy, conf, cls in reversed(det):
                                if save_txt:  # Write to file
                                    xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(
                                        -1).tolist()  # normalized xywh

                                    line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format

                                    # 根目录下txt文档记录位置信息等
                                    x1 = int(xyxy[0])
                                    y1 = int(xyxy[1])
                                    x2 = int(xyxy[2])
                                    y2 = int(xyxy[3])

                                    x_1 = xywh[0]
                                    y_1 = xywh[1]
                                    w_1 = xywh[2]
                                    h_1 = xywh[3]

                                    ww = x2 - x1  # 宽度
                                    hh = y2 - y1  # 高度
                                    ss = ww * hh  # 面积

                                    xx = ww / w_1  # 图片宽度
                                    yy = hh / h_1  # 图片高度
                                    SS = xx * yy  # 图片面积
                                    # print(SS)

                                    zb = ss / SS
                                    xyxy1 = [int(num) for num in xyxy]
                                    xyxy1 = str(xyxy1).replace(" ", "")

                                    current_time = datetime.datetime.now()
                                    formatted_time = current_time.strftime("%Y-%m-%d-%H:%M:%S")

                                    with open(f'{txt_path}.txt', 'a') as f:
                                        f.write(
                                            f"{count} {img_path} {names[int(cls)]} {conf * 100:.2f}% "
                                            f"{xyxy1} {ss} {zb * 100:.2f}% {formatted_time}\n")
                                        # f"ID{count:<5}目标类别是：{names[int(cls)]:<10}"
                                        # f"位置信息x1,y1,y1, y2为: {['%.2f' % num for num in xyxy]}\t "
                                        # f"评判可信度：{conf * 100:.2f}%\n")

                                    # 要保存的数据列表
                                    data1 = [
                                        {
                                            "序号ID": count,
                                            "文件路径": img_path,
                                            "目标类别": names[int(cls)],
                                            "坐标位置": [f'%.2f' % num for num in xyxy],
                                            "评判可信度": f'{conf * 100:.2f}%',
                                            "目标面积": ss,
                                            "目标占比": f'{zb * 100:.2f}%',
                                            "检测时间": formatted_time
                                        }
                                    ]
                                    # 打开CSV文件并写入数据
                                    with open(f'{csv_path}.csv', 'a', newline='') as csv_file:
                                        # 定义CSV写入器
                                        writer = csv.DictWriter(csv_file, fieldnames=["序号ID", "文件路径", "目标类别",
                                                                                      "坐标位置",
                                                                                      "评判可信度", "目标面积",
                                                                                      "目标占比", "检测时间"])

                                        # 写入表头
                                        # writer.writeheader()

                                        # 循环写入数据
                                        for row in data1:
                                            writer.writerow(row)

                                    file_path = save_dir / 'left' / f'{p.stem}.txt'

                                    # 获取文件夹路径
                                    folder_path = os.path.dirname(file_path)

                                    # 检查文件夹是否存在，如果不存在，则创建
                                    if not os.path.exists(folder_path):
                                        os.makedirs(folder_path)

                                    # 如果文件不存在，则创建空文件
                                    if not os.path.exists(file_path):
                                        open(file_path, 'a').close()

                                    with open(file_path, 'a', encoding='utf-8') as file:
                                        # 写入文字到文件中
                                        file.write(
                                            f"{count} {img_name} {names[int(cls)]} {x1} {y1} {x2} {y2} "
                                            f"{ww} {hh} {ss} {zb * 100:.2f}% {conf * 100:.2f}%\n")

                                    with open(fp, 'a', encoding='utf-8') as file:
                                        # 写入文字到文件中
                                        file.write(
                                            f"{count} {img_path} {names[int(cls)]} {conf * 100:.2f}% "
                                            f"{xyxy1} {ss} {zb * 100:.2f}% {formatted_time}\n")

                                        # 序号 文件名称 缺陷类别，X最小值  Y最小值 X最大值 Y最大值
                                        # 缺陷宽度 缺陷高度 缺陷面积 缺陷占比 评判可信度 备注
                                        # file.write(" ".join([f"{int(num):>4}" for num in xyxy]))
                                        # file.write(f" {ww} {hh} {ss}\n")
                                        # file.write(
                                        #        f"{[int(num) for num in xyxy]}\n")
                                        # f"{['%.2f' % num for num in xyxy]}\n")

                                if save_img or save_crop or view_img:  # Add bbox to image
                                    c = int(cls)  # integer class
                                    label = None if hide_labels else (
                                        names[
                                            c] if hide_conf else f'ID{count}: {names[c]} ')  # 创建了要在边界框上显示的标签
                                    #  c] if hide_conf else f'ID{count}: {names[c]} {conf:.2f}')  # 创建了要在边界框上显示的标签
                                    count += 1
                                    annotator.box_label(xyxy, label, color=colors(c, True))  # 绘制标签
                                    names_dic[names[c]] += 1  # 类别统计，数量+1
                                if save_crop:
                                    save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg',
                                                 BGR=True)

                        # 处理后的图像
                        im0 = annotator.result()
                        # 调用send_img方法，发送数据给它，send_img方法绑定这下面的show_image方法，把数据显示在pyqt5上面
                        self.send_img.emit(im0)
                        # if save_img:
                        # 如果等于2，说明用户勾选保存结果了。把结果写入到runs文件夹中
                        if self.is_save_result == 2:
                            # 检测图片
                            if dataset.mode == 'image':
                                cv2.imwrite(save_path, im0)
                            # 检测视频流
                            else:
                                if vid_path[i] != save_path:  # new video
                                    vid_path[i] = save_path
                                    if isinstance(vid_writer[i], cv2.VideoWriter):
                                        vid_writer[i].release()  # release previous video writer
                                    if vid_cap:  # video
                                        fps = vid_cap.get(cv2.CAP_PROP_FPS)
                                        w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                                        h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                                    else:  # stream
                                        fps, w, h = 30, im0.shape[1], im0.shape[0]
                                    save_path = str(
                                        Path(save_path).with_suffix('.mp4'))  # force *.mp4 suffix on results videos
                                    vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps,
                                                                    (w, h))
                                vid_writer[i].write(im0)
                        # 实时发送类别统计信息
                        self.send_detectinfo_dic.emit(names_dic)
                        # 打印结果，t分别是预测、推理、NMS的值
                        t = tuple(x.t / seen * 1E3 for x in dt)
                        # 实时发送检测速度，小数点采取四舍五入，保留一位小数
                        self.detect_speed.emit(str(round(t[0], 1)))
                    # 没有视频帧了，进度达到100跳出循环
                    if progress_percentage == 100:
                        print("检测结束了！")
                        break
        except Exception as e:
            print(f"出错了，出错的问题是：: {e}")

            # raise
        self.send_xunhuan.emit(0)


class SignalEmitter(QObject):
    row_clicked = pyqtSignal(int)


# pyqt5处理方法
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)  # 加载pyqt5UI

        '''
        =======================================================================
        '''
        # 开始之前清空文件夹内的所有数据
        mkdir(save_path1)
        '''
        =======================================================================
        '''

        # --------------------------------------
        # 改变背景
        palette = QPalette()
        palette.setBrush(QPalette.Background, QBrush(QPixmap("icon/77.jpg")))
        self.setPalette(palette)
        # --------------------------------------

        self.tableWidget.horizontalHeader().setStyleSheet(
            "QHeaderView::section{background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
            "stop:0 #fea356, stop: 0.5 #eeeeee,stop: 0.6 #eeeeee, stop:1 #fea356);"
            "border:1px solid rgb(201,202,202);border-left:none;"
            "min-height:25px;min-width:100px;font-weight:bold;font-size:13px};"
        )
        self.tableWidget_2.horizontalHeader().setStyleSheet(
            "QHeaderView::section{background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
            "stop:0 #fea356, stop: 0.5 #eeeeee,stop: 0.6 #eeeeee, stop:1 #fea356);"
            "border:1px solid rgb(201,202,202);border-left:none;"
            "min-height:25px;min-width:100px;font-weight:bold;font-size:13px};"
        )

        self.methoBinding()  # pyqt5和python方法绑定
        self.camera_isOpen = False  # 摄像头开关状态，默认关
        self.my_thread = MyThread()  # 创建线程
        self.my_thread.weights = ''  # 设置pt模型
        # self.my_thread.files = ''  #设置默认保存文件夹
        self.my_thread.source = ''  # 默认检测资源为空
        self.my_thread.send_img.connect(lambda x: self.show_image(x, self.label_result))
        self.my_thread.send_detectinfo_dic.connect(self.show_detect_info)
        self.my_thread.detect_speed.connect(lambda x: self.show_detect_speed(x))
        self.my_thread.send_percent.connect(lambda x: self.progressBar.setValue(x))
        self.my_thread.send_total.connect(self.show_total)
        self.my_thread.send_xunhuan.connect(self.show_biaoge)
        self.load_config()  # 加载配置

    # 方法绑定
    def methoBinding(self):
        self.Button_checkImg_2.clicked.connect(self.select_images)  # 检测图片
        # self.Button_checkVideo.clicked.connect(self.check_video)  # 检测视频
        # self.Button_openCamera.clicked.connect(self.open_camera)  # 打开摄像头
        self.Button_select_folder_3.clicked.connect(self.select_folder)  # 检测文件夹
        self.Button_select_folder_2.clicked.connect(self.select_folder_2)  # 保存文件夹路径
        self.doubleSpinBox_conf.valueChanged.connect(lambda x: self.change_conf(x))  # 调整置信度
        self.doubleSpinBox_iou.valueChanged.connect(lambda x: self.change_iou(x))  # 调整iou
        self.checkBox_isSave.clicked.connect(self.save_result)  # 选择是否保存
        self.Button_select_w_p.clicked.connect(self.select_weights)  # 选择权重
        self.pushButton_bofang.clicked.connect(self.play_and_pause)  # 播放暂停
        self.pushButton_logo.clicked.connect(self.click_logo)  # 点击logo图标
        self.pushButton_info.clicked.connect(self.click_info)  # 点击软件信息图标

    # 显示检测速度，单位是ms
    def show_detect_speed(self, speed):
        self.label_speed.setText(speed + "ms")

    # 控制暂停和播放
    def play_and_pause(self):
        # 如果检测的资源为空，提示先选择资源
        if self.my_thread.source == "":
            QMessageBox.information(self, '错误提示', '你还没选择检测的资源呢！')
            return
        if self.my_thread.play:
            # 原来是播放的，现在暂停了
            self.my_thread.play = False
        else:
            # 原来是暂停的，现在播放了
            self.my_thread.play = True
        # 更新播放和暂停的图标
        self.update_play_icon()

    # 更新播放和暂停的图标
    def update_play_icon(self):
        icon = QtGui.QIcon()
        if self.my_thread.play:
            # 如果现在是播放着的，那么显示的是暂停的图标
            icon.addPixmap(QtGui.QPixmap("icon/暂停_pause.png"),
                           QtGui.QIcon.Normal, QtGui.QIcon.Off)
        else:
            # 如果现在是播放着的，那么显示的是暂停的图标
            icon.addPixmap(QtGui.QPixmap("icon/播放_play.png"),
                           QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_bofang.setIcon(icon)
        self.pushButton_bofang.setIconSize(QtCore.QSize(32, 32))

    # 关闭进程，重置显示窗口
    def clean(self):
        self.my_thread.end_loop = True  # 跳出循环为真
        self.my_thread.play = False  # 设置播放为假
        self.my_thread.source = ''  # 清空检测信息
        self.my_thread.wait()
        self.label_result.clear()  # 清除输出的信息
        self.update_play_icon()
        self.label_result.setPixmap(QtGui.QPixmap("data/images/test.jpg"))

    # 选择图片
    def select_images(self):
        # # 点击选择图片时选择保存文件夹
        # options = QFileDialog.Options()
        # options |= QFileDialog.ReadOnly
        # folder_path = QFileDialog.getExistingDirectory(self, "选择保存文件夹", "", options=options)
        #
        # global save_path1
        # if folder_path:
        #     save_path1 = str(folder_path)
        # else:
        #     save_path1 = 'runs/detect'
        #
        # # 如果需要，可以在这里执行其他操作
        # print("选择的文件夹路径为:", save_path1)

        if self.my_thread.isRunning():
            self.clean()
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        image_path, _ = QFileDialog.getOpenFileName(self, "Select Image", "",
                                                    "Images (*.png *.jpg *.jpeg *.bmp *.gif);;All Files (*)",
                                                    options=options)
        global img_path
        img_path = str(image_path)
        # print(img_path + "我在这里")
        if image_path:
            self.my_thread.source = image_path
            self.start_detect()

        image_name = os.path.basename(image_path).split('.')[0]  # split的作用：以'.'为分界点，将test.xml分为两个部分，成为数组。[0]表示取前
        print("image_name:", image_path + '/' + image_name + '.txt')
        '''
        ===========================================================================判断txt是否存在
        '''
        # checking if file exist or not
        if (os.path.isfile(save_path1 + '/' + image_name + '.txt')):
            # os.remove() function to remove the file
            os.remove(save_path1 + '/' + image_name + '.txt')
            # Printing the confirmation message of deletion
            print("File Deleted successfully")
        else:
            pass

        # checking if file exist or not
        if (os.path.isfile(save_path1 + '/left/' + image_name + '.txt')):
            # os.remove() function to remove the file
            os.remove(save_path1 + '/left/' + image_name + '.txt')
            # Printing the confirmation message of deletion
            print("File Deleted successfully")
        else:
            pass

        # txtpath = save_path1 + '/left/' + image_name + '.txt'
        # open(txtpath)  # 创建文件对象
        #
        # txtpath = save_path1 + '/' + image_name + '.txt'
        # open(txtpath)  # 创建文件对象

    # 选择文件夹

    def select_folder(self):

        # # 点击选择图片时选择保存文件夹
        # options = QFileDialog.Options()
        # options |= QFileDialog.ReadOnly
        # folder_path = QFileDialog.getExistingDirectory(self, "选择保存文件夹", "", options=options)
        #
        # global save_path1
        # if folder_path:
        #     save_path1 = str(folder_path)
        # else:
        #     save_path1 = 'runs/detect'
        #
        # # 如果需要，可以在这里执行其他操作
        # print("选择的文件夹路径为:", save_path1)

        self.clean()
        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹", "", options=options)

        global img_path
        img_path = str(folder_path)

        self.my_thread.source = folder_path
        self.start_detect()

    def select_folder_2(self):

        options = QFileDialog.Options()
        options |= QFileDialog.ReadOnly
        folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹", "", options=options)

        global save_path1
        if folder_path:
            save_path1 = str(folder_path)
            self.line_weights_11.setText(save_path1)
            # self.my_thread.files = save_path1
        else:
            save_path1 = './OUTPUT/'
            self.line_weights_11.setText(save_path1)
            # self.my_thread.files = save_path1
        '''
        =======================================================================
        '''
        # 开始之前清空文件夹内的所有数据
        mkdir(save_path1)
        '''
        =======================================================================
        '''
        # 如果需要，可以在这里执行其他操作
        print("选择的文件夹路径为:", save_path1)

    # def plot_one_box(x, img, color=None, label=None, line_thickness=3):
    #     # Plots one bounding box on image img
    #     tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
    #     color = color or [random.randint(0, 255) for _ in range(3)]
    #     c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
    #     cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    #     ######################################打印坐标#############################
    #     print("左上点的坐标为：(" + str(c1[0]) + "," + str(c1[1]) + ")，右上点的坐标为(" + str(c2[0]) + "," + str(
    #         c1[1]) + ")")
    #     print("左下点的坐标为：(" + str(c1[0]) + "," + str(c2[1]) + ")，右下点的坐标为(" + str(c2[0]) + "," + str(
    #         c2[1]) + ")")
    #     print("中心点的坐标为：(" + str((c2[0] - c1[0]) / 2 + c1[0]) + "," + str((c2[1] - c1[1]) / 2 + c1[1]) + ")")
    #     ##########################################################################
    #     if label:
    #         tf = max(tl - 1, 1)  # font thickness
    #         t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
    #         c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
    #         cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
    #         cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)

    # 展示检测的信息

    def on_item_clicked(self, item):
        row = item.row()  # 获取用户点击的行数
        self.source3 = save_path1 + "/" + "left" + "/" + self.jpg_file_names[row]
        print("当前点击的图片路径为：", self.source3)
        if self.source3:
            source1 = save_path1 + "/" + self.jpg_file_names[row]
            source2 = source1.replace(".jpg", ".txt")
            source1 = cv2.imread(source1)
            self.show_image(source1, self.label_result)
            print(source2)
        with open(source2, 'r') as file:
            # 逐行读取文件内容
            lines = file.readlines()
        # 如果文件中包含多个数组，每个数组以换行分隔，可以使用 split() 方法分割成单独的数组
        arrays = [line.strip() for line in lines]
        num = len(arrays)
        self.tableWidget.setRowCount(num)
        for i in range(1, num + 1):
            s = arrays[i - 1]
            # print(s)
            result = ""
            for h in range(len(s)):
                # 如果当前字符不是空格，或者不是连续空格的最后一个空格，则将其添加到结果中
                if s[h] != " " or (h > 0 and s[h - 1] != " "):
                    result += s[h]
            s = result.split(" ")
            s = [elem.strip() for elem in s]
            for col in range(9):
                if col <= 7:
                    item = QTableWidgetItem(s[col])  # 设置单元格的内容为空格
                    item.setTextAlignment(Qt.AlignCenter)  # 设置内容居中对齐
                    if col == 3:  # 检查是否是第四列（从0开始）
                        a = s[col]
                        b = float(a.strip("%"))
                        # percentage = float(s[col])
                        # print(b)
                        if b > 50:
                            item.setForeground(QColor(0, 128, 0))  # 设置字体颜色为绿色
                        else:
                            item.setForeground(QColor(255, 0, 0))  # 设置字体颜色为红色
                    else:
                        item.setForeground(QColor(0, 0, 0))  # 其他元素字体保持黑色不变

                    self.tableWidget.setItem(i - 1, col, item)  # 将单元格添加到表格中
                    if i % 2 == 0:
                        item.setBackground(QColor(255, 255, 255))  # 设置偶数行背景色为灰色
                    else:
                        item.setBackground(QColor(220, 220, 220))  # 设置奇数行背景色为白
                else:
                    item = QTableWidgetItem("")  # 设置单元格的内容为空格
                    self.tableWidget.setItem(i - 1, col, item)  # 将单元格添加到表格中
                    if i % 2 == 0:
                        item.setBackground(QColor(255, 255, 255))  # 设置偶数行背景色为灰色
                    else:
                        item.setBackground(QColor(220, 220, 220))  # 设置奇数行背景色为白

        self.source4 = self.source2 + "\\" + "left" + "\\" + self.jpg_file_names[row]
        print(self.source4)
        self.source4 = self.source4.replace(".jpg", ".txt")
        with open(self.source4, 'r') as file:
            # 逐行读取文件内容
            lines1 = file.readlines()
        # 如果文件中包含多个数组，每个数组以换行分隔，可以使用 split() 方法分割成单独的数组
        arrays1 = [line.strip() for line in lines1]
        # 获取第三个数组的第三个值
        self.comboBox.clear()
        for i in range(1, len(arrays1) + 1):
            self.comboBox.addItem("ID{}".format(i))
        # self.comboBox.currentIndexChanged.disconnect(self.on_comboBox_changed2)
        self.comboBox.currentIndexChanged.connect(self.on_comboBox_changed2)
        # print("检测数量功能")
        self.line_weights_2.setText(str(len(arrays1)) + "个")

        if len(arrays1) == 0:
            self.line_weights_3.clear()
            self.line_weights_4.clear()
            self.line_weights_5.clear()
            self.line_weights_6.clear()
            self.line_weights_7.clear()
            self.line_weights_8.clear()
            self.line_weights_9.clear()
            self.line_weights_10.clear()

    # 显示检测结果；加了 @staticmethod装饰器就是一个静态的方法，不需要实例化即可使用
    # 这里是下拉框对应的坐标选择

    def show_detect_info(self, names_dic):
        num = 0
        for key, value in names_dic.items():
            num += value
        self.comboBox.clear()
        for i in range(1, num + 1):
            self.comboBox.addItem("ID{}".format(i))
        self.comboBox.currentIndexChanged.connect(self.on_comboBox_changed)
        # print("检测数量功能")
        self.line_weights_2.setText(str(num) + "个")

        if num == 0:
            self.line_weights_3.clear()
            self.line_weights_4.clear()
            self.line_weights_5.clear()
            self.line_weights_6.clear()
            self.line_weights_7.clear()
            self.line_weights_8.clear()
            self.line_weights_9.clear()
            self.line_weights_10.clear()

        '''
        ==================================================================================================================
        '''
        file_path = "1.txt"
        # 读取文件
        with open(file_path, 'r') as file:
            # 逐行读取文件内容
            lines = file.readlines()
        # 如果文件中包含多个数组，每个数组以换行分隔，可以使用 split() 方法分割成单独的数组
        arrays = [line.strip() for line in lines]
        # 根据选择执行不同的功能
        s = arrays[0]
        result = ""
        for i in range(len(s)):
            # 如果当前字符不是空格，或者不是连续空格的最后一个空格，则将其添加到结果中
            if s[i] != " " or (i > 0 and s[i - 1] != " "):
                result += s[i]
        s = result.split(" ")
        s = [elem.strip() for elem in s]
        # print(s)
        self.line_weights_3.setText(s[3])
        self.line_weights_4.setText(s[5])
        self.line_weights_5.setText(s[4])
        self.line_weights_6.setText(s[6])
        self.line_weights_7.setText(s[2])
        self.line_weights_8.setText(s[11])
        self.line_weights_9.setText(s[9])
        self.line_weights_10.setText(s[10])
        '''
        ==================================================================================================================
        '''

        file_path = "2.txt"
        # 读取文件
        with open(file_path, 'r', encoding='utf-8') as file:
            # 逐行读取文件内容
            lines = file.readlines()
        # 如果文件中包含多个数组，每个数组以换行分隔，可以使用 split() 方法分割成单独的数组
        arrays = [line.strip() for line in lines]
        self.tableWidget.setRowCount(num)
        for i in range(1, num + 1):
            s = arrays[i - 1]
            # print(s)
            result = ""
            for h in range(len(s)):
                # 如果当前字符不是空格，或者不是连续空格的最后一个空格，则将其添加到结果中
                if s[h] != " " or (h > 0 and s[h - 1] != " "):
                    result += s[h]
            s = result.split(" ")
            s = [elem.strip() for elem in s]
            for col in range(9):
                if col <= 7:
                    item = QTableWidgetItem(s[col])  # 设置单元格的内容为空格
                    item.setTextAlignment(Qt.AlignCenter)  # 设置内容居中对齐

                    if i % 2 == 0:
                        item.setBackground(QColor(255, 255, 255))  # 设置偶数行背景色为灰色
                    else:
                        item.setBackground(QColor(220, 220, 220))  # 设置奇数行背景色为白

                    if col == 3:  # 检查是否是第四列（从0开始）
                        a = s[col]
                        b = float(a.strip("%"))
                        # percentage = float(s[col])
                        # print(b)
                        if b > 50:
                            # brush = QBrush(QColor(255, 255, 0))
                            # item.setBackground(brush)
                            item.setForeground(QColor(0, 128, 0))  # 设置字体颜色为绿色
                        else:
                            item.setForeground(QColor(255, 0, 0))  # 设置字体颜色为红色
                    else:
                        item.setForeground(QColor(0, 0, 0))  # 其他元素字体保持黑色不变

                    self.tableWidget.setItem(i - 1, col, item)  # 将单元格添加到表格中

                else:
                    item = QTableWidgetItem("")  # 设置单元格的内容为空格
                    self.tableWidget.setItem(i - 1, col, item)  # 将单元格添加到表格中
                    if i % 2 == 0:
                        item.setBackground(QColor(255, 255, 255))  # 设置偶数行背景色为灰色
                    else:
                        item.setBackground(QColor(220, 220, 220))  # 设置奇数行背景色为白

    def on_comboBox_changed(self):
        file_path = "1.txt"

        # 读取文件
        with open(file_path, 'r') as file:
            # 逐行读取文件内容
            lines = file.readlines()
        # 如果文件中包含多个数组，每个数组以换行分隔，可以使用 split() 方法分割成单独的数组
        arrays = [line.strip() for line in lines]
        # 获取第三个数组的第三个值
        selected_option = self.comboBox.currentText()
        nums = self.comboBox.count()
        # 根据选择执行不同的功能
        for i in range(1, nums + 1):
            if selected_option == "ID{}".format(i):
                # print("ID{}".format(i))
                s = arrays[i - 1]
                result = ""
                for i in range(len(s)):
                    # 如果当前字符不是空格，或者不是连续空格的最后一个空格，则将其添加到结果中
                    if s[i] != " " or (i > 0 and s[i - 1] != " "):
                        result += s[i]
                s = result.split(" ")
                s = [elem.strip() for elem in s]
                # print(s)
                self.line_weights_3.setText(s[3])
                self.line_weights_4.setText(s[5])
                self.line_weights_5.setText(s[4])
                self.line_weights_6.setText(s[6])
                self.line_weights_7.setText(s[2])
                self.line_weights_8.setText(s[11])
                self.line_weights_9.setText(s[9])
                self.line_weights_10.setText(s[10])
        self.comboBox.currentIndexChanged.disconnect(self.on_comboBox_changed)

    def on_comboBox_changed2(self):
        file_path = self.source4
        with open(file_path, 'r') as file:
            # 逐行读取文件内容
            lines = file.readlines()
        # 如果文件中包含多个数组，每个数组以换行分隔，可以使用 split() 方法分割成单独的数组
        arrays = [line.strip() for line in lines]
        # 获取第三个数组的第三个值
        selected_option = self.comboBox.currentText()
        nums = self.comboBox.count()
        # 根据选择执行不同的功能
        for i in range(1, nums + 1):
            if selected_option == "ID{}".format(i):
                # print("ID{}".format(i))
                s = arrays[i - 1]
                result = ""
                for i in range(len(s)):
                    # 如果当前字符不是空格，或者不是连续空格的最后一个空格，则将其添加到结果中
                    if s[i] != " " or (i > 0 and s[i - 1] != " "):
                        result += s[i]
                s = result.split(" ")
                s = [elem.strip() for elem in s]
                # print(s)
                self.line_weights_3.setText(s[3])
                self.line_weights_4.setText(s[5])
                self.line_weights_5.setText(s[4])
                self.line_weights_6.setText(s[6])
                self.line_weights_7.setText(s[2])
                self.line_weights_8.setText(s[11])
                self.line_weights_9.setText(s[9])
                self.line_weights_10.setText(s[10])

    @staticmethod
    def show_image(image_path, label):
        try:
            ih, iw, _ = image_path.shape
            w = label.geometry().width()
            h = label.geometry().height()
            if iw / w > ih / h:
                scal = w / iw
                nw = w
                nh = int(scal * ih)
                img_src_ = cv2.resize(image_path, (nw, nh))
            else:
                scal = h / ih
                nw = int(scal * iw)
                nh = h
                img_src_ = cv2.resize(image_path, (nw, nh))

            frame = cv2.cvtColor(img_src_, cv2.COLOR_BGR2RGB)
            img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[2] * frame.shape[1],
                         QImage.Format_RGB888)
            label.setPixmap(QPixmap.fromImage(img))
            label.setAlignment(Qt.AlignCenter)

        except Exception as e:
            print(repr(e))

    def save_result(self):
        if (self.checkBox_isSave.checkState() == 0):
            # 如果现在等于0，那么也就是之前等于2。点击后应该变为0
            self.my_thread.is_save_result = 0
        else:
            self.my_thread.is_save_result = 2

    # 选择权重
    def select_weights(self):
        options = QFileDialog.Options()
        image_path, _ = QFileDialog.getOpenFileName(self, '选择pt权重', '', 'pt (*.pt)', options=options)
        if image_path:
            self.line_weights.setText(image_path)
            self.my_thread.weights = image_path
            self.clean()
            self.update_play_icon()

    # 显示检测类别信息统计
    def show_total(self, total_dic):
        try:
            self.resultWidget.clear()
            total_dic = sorted(total_dic.items(), key=lambda x: x[1], reverse=True)
            total_dic = [i for i in total_dic if i[1] > 0]
            results = [' ' + str(i[0]) + '：' + str(i[1]) for i in total_dic]
            self.groupBox_outInfo
            self.resultWidget.addItems(results)
        except Exception as e:
            print(repr(e))

    def show_biaoge(self, s):
        if s == 0:
            self.source2 = save_path1
            # self.source2 = r"E:\CODE\PYQT3.0\runs\detect\exp130"
            # self.source2 = r"C:\Users\15686\Desktop\PYQT2.0\PYQT2.0\runs\detect\exp98\exp98"
            # 获取文件夹中所有的 .jpg 图片文件路径
            jpg_files = glob.glob(os.path.join(self.source2, '*.jpg'))

            # 提取图片文件名
            self.jpg_file_names = [os.path.basename(file) for file in jpg_files]
            # self.tableWidget_2.clear()
            self.tableWidget_2.setRowCount(len(self.jpg_file_names))
            self.tableWidget_2.verticalHeader().setVisible(False)
            for row, file_name in enumerate(self.jpg_file_names):
                item = QTableWidgetItem(file_name)
                self.tableWidget_2.setItem(row, 0, item)

            self.signal_emitter = SignalEmitter()

            # 连接表格的itemClicked信号到槽函数
            self.tableWidget_2.itemClicked.connect(self.on_item_clicked)

    def check_video(self):
        self.clean()
        options = QFileDialog.Options()
        video_path, _ = QFileDialog.getOpenFileName(self, '选择视频', '',
                                                    'Videos (*.mp4 *.avi *.mkv);;All Files (*)', options=options)
        if video_path:
            self.my_thread.source = video_path
            self.start_detect()

    # 获取电脑摄像头的数量
    def get_camera_num(self):
        num = 0  # 计数器，用于记录找到的摄像头数量
        deviceList = []  # 列表，用于存储找到的摄像头设备索引

        for i in range(3):  # 遍历预设的摄像头数量范围,因为电脑一般不超过3个摄像头
            stream = cv2.VideoCapture(i, cv2.CAP_DSHOW)  # 使用OpenCV库创建一个视频捕获对象，指定设备索引和捕获模式
            exists = stream.grab()  # 尝试从摄像头捕获一帧图像是否存在
            stream.release()  # 释放视频捕获对象
            if not exists:  # 如果未能成功捕获图像，则跳过当前设备继续循环
                continue
            else:
                num += 1  # 如果成功捕获图像，数量+1
                deviceList.append(i)  # 把对应的索引加入设备列表中
        return num, deviceList

    # 开启/关闭摄像头检测
    def open_camera(self):
        self.clean()
        self.dialog = QDialog(self.centralwidget)  # 创建摄像头弹窗
        self.dialog.setWindowTitle("请选择你的摄像头")  # 设置弹窗标题
        self.dialog.resize(400, 200)

        # 判断摄像头开启状态，如何开着设置为关，反之则开
        if self.camera_isOpen == True:
            # 开着的，现在关闭
            self.camera_isOpen = False
            # 跳出循环结束

        else:
            num, deviceList = self.get_camera_num()

            # 如果没有摄像头，弹出提示框
            if num == 0:
                QMessageBox.warning(self, "出错啦", "<p>未检测到有效的摄像头</p>")
            # 如果有摄像头，弹出按钮，点击按钮后赋值对应的摄像头并打开开始检测
            else:
                self.camera_isOpen = True
                # 设置水平布局
                h_layout = QHBoxLayout(self.dialog)
                for xuhao in deviceList:
                    button = QPushButton(f'摄像头{xuhao}', self.dialog)
                    button.clicked.connect(lambda _, x=xuhao: self.click_camera(x))
                    h_layout.addWidget(button)  # 为按钮添加布局
                # 显示对话框
                self.dialog.exec_()

    # 弹窗中选择摄像头
    def click_camera(self, device):
        self.my_thread.source = device
        self.start_detect()
        # 关闭对话框
        self.dialog.close()

    # 点击logo
    def click_logo(self):
        QMessageBox.about(self.centralwidget, "关于作者",
                          "西安工业大学底片项目研究组")

    # 点击软件信息
    def click_info(self):
        QMessageBox.about(self.centralwidget, "帮助", "版本1.0  "
                                                      "更新时间：2024.1.24")

    # 修改置信度
    def change_conf(self, x):
        self.conf = round(x, 2)

    # 修改IOU
    def change_iou(self, x):
        self.iou = round(x, 2)

    # 从json中读取配置
    def load_config(self):
        # 读取 JSON 文件
        with open("config.json", "r") as jsonfile:
            loaded_config = json.load(jsonfile)
            # 获取参数值
            self.my_thread.weights = loaded_config["weights"]
            self.conf = loaded_config["conf"]
            self.iou = loaded_config["iou"]
            self.save_path = loaded_config["save_path"]
            self.is_save_result = loaded_config["is_save_result"]

        # 写入到pyqt5中
        self.doubleSpinBox_conf.setProperty("value", self.conf)
        self.doubleSpinBox_iou.setProperty("value", self.iou)
        self.line_weights.setText(self.my_thread.weights)
        # self.line_weights_11.setText(self.my_thread.files)

        if self.is_save_result == 2:
            self.my_thread.is_save_result = 2
            # 如果等于2，那么勾上复选框True
            self.checkBox_isSave.setChecked(True)

    # 关闭窗口事件
    def closeEvent(self, event):
        confirm = QMessageBox.question(self, '关闭程序', '确定关闭？',
                                       QMessageBox.Yes | QMessageBox.No)

        if confirm == QMessageBox.Yes:
            # 确认关闭，保存用户数据
            config = dict()  # 创建一个空的字典
            config["conf"] = self.conf
            config["iou"] = self.iou
            config["weights"] = self.my_thread.weights
            config["save_path"] = self.save_path
            config["is_save_result"] = self.checkBox_isSave.checkState()
            # 将修改后的配置参数写入 JSON 文件
            with open("config.json", "w") as jsonfile:
                json.dump(config, jsonfile, indent=4)
            event.accept()
        else:
            event.ignore()

    # 开始检测,play为真，跳出循环为假，star线程,更新播放图标
    def start_detect(self):
        self.my_thread.play = True
        self.my_thread.end_loop = False  # 跳出循环为真
        self.update_play_icon()
        self.my_thread.start()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWin = MainWindow()
    myWin.show()
    sys.exit(app.exec_())
